package android.hermes;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.*;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
import java.lang.Number;


import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hermes.R.id;
import android.hermes.R.layout;
import android.hermes.R.raw;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;


/**
 *  
 * @author Erik Tevell, Joakim Nels�n & Tobias Johansson
 *
 */

@SuppressWarnings("unused")
public class Hermes extends Activity implements OnClickListener {

    //Declaration of instance variables
    private WifiManager wifi = null;
    private Button sendButton; //listenButton;
    private EditText fileBox, idBox, hopBox, msgBox;
    private TextView fileText;
    private AdHocWifiManager adhocWifi;
    private InetAddress myIP;
    private ArrayList<InetAddress> friends;
    private static final int myID = 3;
    protected ArrayList<HermesMessage> sendBuffer;
    private ArrayList<Integer> myBuffer;

    public static final String NEW_FRIEND_FOUND = "New hermes unit found";

    /* (non-Javadoc)
     * @see android.app.Activity#onCreate(android.os.Bundle)
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //Create the manager for Ad Hoc Wifi
        adhocWifi = new AdHocWifiManager();

        sendBuffer = new ArrayList<HermesMessage>();
        friends = new ArrayList<InetAddress>();
        myBuffer = new ArrayList<Integer>();

        sendButton = (Button) findViewById(R.id.sendButton);
        sendButton.setOnClickListener(this);

        //The box that handles destination id
        idBox = (EditText) findViewById(R.id.idBox);
        //The box that handles hop count
        hopBox = (EditText) findViewById(R.id.hopBox);
        //The box that handles message to destination
        msgBox = (EditText) findViewById(R.id.msgBox);
        //The box that handles filename
        fileBox = (EditText) findViewById(R.id.fileBox);
        //msg that will be shown on screen
        fileText = (TextView) findViewById(R.id.file);



        //Disable the usual WiFi
        wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        if(wifi.isWifiEnabled()) wifi.setWifiEnabled(false);

        //Enables AdHoc wifi
        adhocWifi.enableWifi();
        try {
            friends.add(InetAddress.getByName(adhocWifi.getMyIP()));
            fileText.setText(fileText.getText() + "\nID: "+myID+" "+ adhocWifi.getMyIP());
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //create dir and move bin
        createDirs();
        moveFiles();

        //Register receiver to handle some message passing,
        quickRegister();

        //Start listening for messages from other hermes devices
        startListeningForMsg();

        //Start listening for announcements from nearby devices.
        startListeningForPing();  

        //Ping other hermes devices
        pingOthers();

    }

    /* (non-Javadoc)
     * @see android.view.View.OnClickListener#onClick(android.view.View)
     */
    public void onClick(View v) {
        if(v == sendButton){
            popMsg("Adding to send buffer, will be forwarded\n" +
                    " to the destination as soon as possible.\n" +
                    "Thank you for choosing:\n" +
            "Hermes(TM) Epidemic version");

            //starts sending
            addToSendBuffer();
        }
        else{
            popMsg("button error");
        }
    }

    //	/**
    //	 * (non-Javadoc)
    //	 * @see android.os.Handler#handleMessage(android.os.Message)
    //	 * 
    //	 */
    //	private Handler handler = new Handler() {
    //		@Override
    //		public void handleMessage(Message msg) {
    //			String x = (String) msg.obj;
    //			//Context context = getApplicationContext();
    //			fileText.setText(fileText.getText() + "\n" +x );			
    //			popMsg(x);
    //		}
    //	};
    /**
     * 
     */
    private void addToSendBuffer(){
        long created = System.currentTimeMillis();
        HermesMessage m = new HermesMessage(Integer.parseInt(idBox.getText().toString()), myID, Integer.parseInt(hopBox.getText().toString()), created, fileBox.getText().toString(), msgBox.getText().toString());
        sendBuffer.add(m);
        myBuffer.add(m.hashCode());
        popMsg("Hash: " + m.hashCode());
    }

    /**
     * Sets up socket to static IP(otherIP),
     * creates a DataOutputStream connected to that socket
     * and calls the encode method in MsgHandler with args from input-screen

     */
    private void send(InetAddress ip, int destId, ArrayList<Integer> dontSendTheseMsg){
        try {

            //Create a new list of messages from the sendBuffer, containing only those 
            //which hashCode does not match any in the list of codes the other unit sent us.
            ArrayList<HermesMessage> tempList = new ArrayList<HermesMessage>();
            for(int j = 0; j<sendBuffer.size();j++){
                HermesMessage outgoing = sendBuffer.get(j);
                if(!dontSendTheseMsg.contains(outgoing.hashCode())) tempList.add(outgoing);
            }
            tempList.trimToSize();

            //If this new list isn't empty, we have some messages the other unit does not,
            //and so we send them to the IP-address provided
            if(!tempList.isEmpty()){
                Socket sendSocket = new Socket(ip, 6546);
                DataOutputStream utdata = new DataOutputStream(sendSocket.getOutputStream());

                for(int i=0; i<tempList.size(); i++){

                    HermesMessage current = tempList.get(i);

                    if(current.getDestination() == destId && current.getHopCount() > 0){
                        byte[] n = current.encodeMsg();
                        utdata.write(n);
                        //sendBuffer.remove(current);
                    }
                    else if(current.getHopCount() > 1 ){
                        byte[] n = current.encodeMsg();
                        utdata.write(n);
                    }
                }

                tempList.clear();		
                sendSocket.close();
            }
        } catch (Exception e) {
            //error handling
            //popMsg("Send error");
            e.getMessage();
        } 

    }

    /**
     * creates and starts a thread that handles incoming stream of data
     * and calls the decode method in MsgHandler
     */
    private void startListeningForMsg(){
        Thread t = new Thread (new Runnable(){
            public void run(){
                while(true){
                    Socket connectionSocket;
                    DataInputStream indata;
                    //    			Message msg = new Message();

                    try {
                        ServerSocket welcomeSocket = new ServerSocket(6546);			
                        connectionSocket = welcomeSocket.accept();

                        indata = new DataInputStream(connectionSocket.getInputStream());
                        while(indata.available() < 1) {};
                        HermesMessage m = new HermesMessage(indata);
                        myBuffer.add(m.hashCode());
//                        fileText.setText(m.hashCode());
                        //////////////////////////////////////////////////////////////////////////////////////////////////////////					
                        //Message for me?
                        if(m.getDestination() == myID){
                            if(m.getFileName().length() > 0){
                                File file = new File("/data/data/android.hermes/download/"+m.getFileName());
                                FileOutputStream FOS = new FileOutputStream(file);
                                FOS.write(m.getData());

                            }
                            informMain(42, connectionSocket.getInetAddress());
                            //////////////////////////////////////////////////////////////////////////////////////////////////////////						
                            //						msg.obj = "ID " +m.getAuthor() +" says " +m.getMsg();
                            //						handler.sendMessage(msg);
                            m.flushData();

                        }
                        //Message not for me
                        else{
                            if(m.getFileName().length() > 0){
                                File file = new File("/data/data/android.hermes/send/"+m.getFileName());
                                FileOutputStream FOS = new FileOutputStream(file);
                                FOS.write(m.getData());

                            }

                            m.decHopCount();
                            sendBuffer.add(m);

                            //msg.obj = "A new message was put in sendBuffer";
                            //handler.sendMessage(msg);

                        }
                        indata.close();
                        connectionSocket.close();
                        welcomeSocket.close();

                    } catch (IOException e) {
                        //error handling
                        //					msg.obj = e.toString();
                        //					handler.sendMessage(msg);
                    }
                }
            }
        });
        t.start();
    }
    /**
     * @param message
     * Displays a simple toast message
     */
    public void popMsg(String message) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }

    /**
     * 
     */
    public void quickRegister() {
        IntentFilter filter;
        PingReceiver receiver;
        filter = new IntentFilter(NEW_FRIEND_FOUND);
        receiver = new PingReceiver();
        registerReceiver(receiver, filter);
    }

    /**
     * @param ip
     */
    private void informMain(int ID, InetAddress ip) {
        //Create an intent to send and put the IP-address in it
        Intent intent = new Intent(NEW_FRIEND_FOUND);
        intent.putExtra("ip", ip);
        intent.putExtra("ID", ID);
        //send the intent
        sendBroadcast(intent);    		
    }

    private byte[] myBufferToByteArray() {

        myBuffer.trimToSize();

        //Allocate space for the byte array that will
        //make up the packet we send as a "ping"
        byte[] buf = new byte[4*myBuffer.size()+1];

        //Set the first byte to represent the device's ID
        buf[0] = (byte) myID;

        //Convert, int by int, every hashcode for all the messages
        //we're carrying into bytes in groups of four
        for(int x=0; x < myBuffer.size(); x++){
            int value = myBuffer.get(x);

            buf[(4*x)+1] = (byte) (value >>> 24);
            buf[(4*x)+2] = (byte) (value >>> 16);
            buf[(4*x)+3] = (byte) (value >>> 8);
            buf[(4*x)+4] = (byte)  value;
        }
        return buf;
    }

    /**
     * 
     */
    private void pingOthers () {
        Thread ER = new Thread(new Runnable() {
            public void run() {
                long ping = System.currentTimeMillis();
                try {
                    DatagramSocket sockan = new DatagramSocket();
                    InetAddress target;
                    DatagramPacket packet;

                    while(true){
                        if((System.currentTimeMillis()-ping) >= 5000){

                            byte[] buf = myBufferToByteArray();	


                            for(int i=1; i<255;i++) {
                                target = InetAddress.getByName("192.168.42." + i);
                                if(!friends.get(0).equals(target)){
                                    packet = new DatagramPacket(buf, buf.length, target, 24242);
                                    sockan.send(packet);
                                }
                            }
                            ping = System.currentTimeMillis();
                        }
                    }
                }
                catch (Exception e){
                    e.getMessage();
                }
            }
        });
        ER.start();
    }

    /**
     * 
     */
    private void startListeningForPing () {
        Thread t = new Thread(new Runnable() {
            public void run() {

                InetAddress otherIP = null;
                int receiverID = 0;
                ArrayList<Integer> msgTheOtherAlreadyHave = new ArrayList<Integer>();
                int assassin = 0;

                while(true) {    			
                    try {
                        
                        //Make room for a packet containing an ID and two hashCodes. Very arbitrary.
                        byte[] buf = new byte[89];
                        //buf[0] = (byte) 0;				
                        DatagramPacket packet = new DatagramPacket(buf, buf.length);
                        DatagramSocket ds = new DatagramSocket(24242);
                        
                        
                        //Wait for an incoming message; Once received, extract the senders IP-address.
                        ds.receive(packet);
                        otherIP = packet.getAddress();
                        
                        //Create a new byte array from the received packet's data,
                        //extract the sender's ID from the first place.
                        byte[] data = packet.getData();
                        receiverID = (int) data[0];
                        
                        //Redundant check, will be [9]. But converts the data from
                        //bytes to integer(hashcodes) and puts them in a list, which will be
                        //sent to the send-method.
                        if(data.length>1){
                            byte[] xs = new byte[data.length-1];
                            System.arraycopy(data, 1, xs, 0, xs.length);
                            ByteArrayInputStream bs = new ByteArrayInputStream(xs);
                            DataInputStream dis = new DataInputStream(bs);
                            for(int j = 0; j < xs.length; j+=4) {
                                assassin = dis.readInt();
                                
                                if(assassin!=0) msgTheOtherAlreadyHave.add(assassin);
                            }

                        }	

                        msgTheOtherAlreadyHave.trimToSize();

                        //if we dont "know" this ip yet...
                        if(receiverID != myID && !friends.contains(otherIP)){

                            //...add it to our list of friends!
                            friends.add(otherIP);

                            //Pass the friends IP-address on to the main thread
                            informMain(receiverID, otherIP);

                        }

                        //Acknowledge sender by replying
                        //buf = myBufferToByteArray();
                        //DatagramSocket sockan = new DatagramSocket();
                        //packet = new DatagramPacket(buf, buf.length, otherIP, 24242);
                        //sockan.send(packet);
                        //sockan.close();
                        ds.close();

                        send(otherIP, receiverID, msgTheOtherAlreadyHave);
                        msgTheOtherAlreadyHave.clear();
                        otherIP = null;
                        receiverID = 0;

                    } 
                    catch (Exception e) {
                        //Exception
                        String s = e.getMessage();
                        int lalla = 0;
                    }
                }
            }
        });
        t.start();
    }














    /**
     * Create the necessary directories
     */
    private void createDirs() {
        File dir = new File("/data/data/android.hermes/");
        if (dir.exists() == false) {
            Hermes.this.popMsg("Application data-dir does not exist!");
        }
        else {
            dir = new File("/data/data/android.hermes/download");
            if (dir.exists() == false) {
                if (!dir.mkdir()) {
                    Hermes.this.popMsg("Couldn't create download-directory!");
                }
            }
            dir = new File("/data/data/android.hermes/send");
            if (dir.exists() == false) {
                if (!dir.mkdir()) {
                    Hermes.this.popMsg("Couldn't create send-directory!");
                }
            }
            dir = new File("/data/data/android.hermes/conf");
            if (dir.exists() == false) {
                if (!dir.mkdir()) {
                    Hermes.this.popMsg("Couldn't create conf-directory!");
                }
            }
            dir = new File("/data/data/android.hermes/files");
            if (dir.exists() == false) {
                if (!dir.mkdir()) {
                    Hermes.this.popMsg("Couldn't create files-directory!");
                }
            }
        }

    }

    /**
     * Copies the essential files to their places, and makes some executable.
     */
    private void moveFiles() {
        this.copyBinary("/data/data/android.hermes/conf/tiwlan.ini", R.raw.tiwlan_ini);

        //test file for transfer
        this.copyBinary("/data/data/android.hermes/files/test.txt", R.raw.test_txt);
        this.copyBinary("/data/data/android.hermes/files/test2.txt", R.raw.test2_txt);
    }

    /**
     * @param filename
     * @param resource
     * Creates a file from a resource, "copying" it.
     */
    private void copyBinary(String filename, int resource) {
        File outFile = new File(filename);
        InputStream is = this.getResources().openRawResource(resource);
        byte buf[]=new byte[1024];
        int len;
        try {
            OutputStream out = new FileOutputStream(outFile);
            while((len = is.read(buf))>0) {
                out.write(buf,0,len);
            }
            out.close();
            is.close();
        } catch (IOException e) {
            Hermes.this.popMsg("Couldn't install file - "+filename+"!");
        }
    }

    class PingReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context c, Intent i) {
            Bundle b = i.getExtras();
            fileText.setText(fileText.getText() + "\nID: " + b.get("ID") + " " + ((InetAddress) b.get("ip")).toString().substring(1));
            //popMsg("The other IP is" + (InetAddress) b.get("ip") + "\nwith ID #" + b.get("ID") + "\nMyIP is " + adhocWifi.getMyIP());
        }
    }
}

